﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.Drawing;
using System.Threading.Tasks;
using System.Drawing.Imaging;

namespace VideoSource
{
  public class PS3EyeVideoSource : IVideoSource, IDisposable
  {
    public const string PS3EYE_PREFIX = @"PS3Eye.";


    IntPtr _hCamera;
    Guid _camGuid;
    int _camIdx;

    Size _frameSize;
    int _fps;

    int _frameCount;
    volatile int _byteCount;

    CancellationTokenSource _cts;
    Task _captureTask;

    Bitmap _camBuffer;

    public PS3EyeVideoSource(int camIdx, int fps)
    {
      _camIdx = camIdx;
      _fps = fps;
      _cts = new CancellationTokenSource();

      int camCount = PS3EyeNative.CLEyeGetCameraCount();
      if (camCount <= _camIdx)
      {
        throw new ArgumentOutOfRangeException("camIdx");
      }
      Initialize();
    }

    private void Initialize()
    {
      _camGuid = PS3EyeVideoSource.CameraUUID(_camIdx);
      _hCamera = PS3EyeNative.CLEyeCreateCamera(
        _camGuid,
        CLEyeCameraColorMode.CLEYE_COLOR_RAW,
        CLEyeCameraResolution.CLEYE_VGA,
        _fps);

      int w = 0, h = 0;
      PS3EyeNative.CLEyeCameraGetFrameDimensions(_hCamera, ref w, ref h);
      //PS3EyeNative.CLEyeGetCameraParameter(CLEyeCameraParameter.)
      _frameSize = new Size(w, h);
      if (_frameSize == Size.Empty)
      {
        throw new Exception("Unable to get a meaningful frame size");
      }

      _camBuffer = new Bitmap(w, h, PixelFormat.Format32bppRgb);

    }

    public void Dispose()
    {
      if (Running)
      {
        SignalToStop();
        // Don't wait.
        // Implicitly, if in Running state, _hCamera must have a non zero value
        PS3EyeNative.CLEyeCameraStop(_hCamera);
      }

      if (_hCamera != IntPtr.Zero)
      {
        PS3EyeNative.CLEyeDestroyCamera(_hCamera);
        _hCamera = IntPtr.Zero;
      }
    }

    public event CameraEventHandler NewFrame;

    public string VideoSource
    {
      get
      {
        return PS3EYE_PREFIX + _camIdx;
      }
      set
      {
        throw new NotImplementedException();
      }
    }

    public string Login
    {
      get
      {
        return null;
      }
      set
      {
      }
    }

    public string Password
    {
      get
      {
        return null;
      }
      set
      {
      }
    }

    public int FramesReceived
    {
      get
      {
        int frameCount = 0;//Interlocked.Exchange(ref _frameCount, 0);
        return frameCount;
      }
    }

    public int BytesReceived
    {
      get
      {
        //throw new NotImplementedException();
        int byteCount = Interlocked.Exchange(ref _byteCount, 0);
        return byteCount;
      }
    }

    public object UserData
    {
      get
      {
        throw new NotImplementedException();
      }
      set
      {
        throw new NotImplementedException();
      }
    }

    public bool Running
    {
      get
      {
        return _captureTask != null && !_captureTask.IsCompleted;
      }
    }

    public void Start()
    {
      if (Running)
      {
        throw new InvalidOperationException("Already started");
      }

      bool isStarted = PS3EyeNative.CLEyeCameraStart(_hCamera);
      if (!isStarted)
      {
        throw new Exception("Unable to start camera");
      }

      //PS3EyeNative.CLEyeSetCameraParameter(_hCamera, CLEyeCameraParameter.CLEYE_AUTO_EXPOSURE, 1);

      _captureTask = Task.Factory.StartNew(
        Capture,
        _cts.Token,
        TaskCreationOptions.LongRunning);
    }


    internal int FrameByteSize
    {
      get
      {
        return
          _frameSize.Width * _frameSize.Height *
          (Image.GetPixelFormatSize(_camBuffer.PixelFormat) >> 3);
      }
    }

    internal void Capture(object state)
    {
      var ct = (CancellationToken)state;
      while (!ct.IsCancellationRequested)
      {
        BitmapData bmpData = null;
        bool isCaptured = false;
        try
        {
          bmpData = _camBuffer.LockBits(
            new Rectangle(Point.Empty, _frameSize),
            ImageLockMode.ReadWrite,
            PixelFormat.Format32bppRgb);

          isCaptured = PS3EyeNative.CLEyeCameraGetFrame(_hCamera, bmpData.Scan0, 50);

        }
        finally
        {
          if (bmpData != null)
          {
            _camBuffer.UnlockBits(bmpData);
          }
        }

        if (isCaptured)
        {
          try
          {
            //Interlocked.Increment(ref _frameCount);            
            if (NewFrame != null)
            {
              NewFrame(this, new CameraEventArgs(_camBuffer));
            }
          }
          catch
          {
            // don't handle, its a user defined method
          }
        }
      }
    }

    public void SignalToStop()
    {
      _cts.Cancel();
    }

    public void WaitForStop()
    {
      Task.WaitAll(_captureTask);
      PS3EyeNative.CLEyeCameraStop(_hCamera);
    }

    public void Stop()
    {
      SignalToStop();
      WaitForStop();
    }

    public static int CameraCount { get { return PS3EyeNative.CLEyeGetCameraCount(); } }
    public static Guid CameraUUID(int idx) { return PS3EyeNative.CLEyeGetCameraUUID(idx); }


  }

  // camera color mode
  public enum CLEyeCameraColorMode
  {
    CLEYE_MONO_PROCESSED,
    CLEYE_COLOR_PROCESSED,
    CLEYE_MONO_RAW,
    CLEYE_COLOR_RAW,
    CLEYE_BAYER_RAW
  }

  // camera resolution
  public enum CLEyeCameraResolution
  {
    CLEYE_QVGA,
    CLEYE_VGA
  }
  // camera parameters
  public enum CLEyeCameraParameter
  {
    // camera sensor parameters
    CLEYE_AUTO_GAIN,			// [false, true]
    CLEYE_GAIN,					// [0, 79]
    CLEYE_AUTO_EXPOSURE,		// [false, true]
    CLEYE_EXPOSURE,				// [0, 511]
    CLEYE_AUTO_WHITEBALANCE,	// [false, true]
    CLEYE_WHITEBALANCE_RED,		// [0, 255]
    CLEYE_WHITEBALANCE_GREEN,	// [0, 255]
    CLEYE_WHITEBALANCE_BLUE,	// [0, 255]
    // camera linear transform parameters
    CLEYE_HFLIP,				// [false, true]
    CLEYE_VFLIP,				// [false, true]
    CLEYE_HKEYSTONE,			// [-500, 500]
    CLEYE_VKEYSTONE,			// [-500, 500]
    CLEYE_XOFFSET,				// [-500, 500]
    CLEYE_YOFFSET,				// [-500, 500]
    CLEYE_ROTATION,				// [-500, 500]
    CLEYE_ZOOM,					// [-500, 500]
    // camera non-linear transform parameters
    CLEYE_LENSCORRECTION1,		// [-500, 500]
    CLEYE_LENSCORRECTION2,		// [-500, 500]
    CLEYE_LENSCORRECTION3,		// [-500, 500]
    CLEYE_LENSBRIGHTNESS		// [-500, 500]
  }

  public static class PS3EyeNative
  {
    #region [ CLEyeMulticam Imports ]
    [DllImport("CLEyeMulticam.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int CLEyeGetCameraCount();
    [DllImport("CLEyeMulticam.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern Guid CLEyeGetCameraUUID(int camId);
    [DllImport("CLEyeMulticam.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr CLEyeCreateCamera(Guid camUUID, CLEyeCameraColorMode mode, CLEyeCameraResolution res, float frameRate);
    [DllImport("CLEyeMulticam.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool CLEyeDestroyCamera(IntPtr camera);
    [DllImport("CLEyeMulticam.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool CLEyeCameraStart(IntPtr camera);
    [DllImport("CLEyeMulticam.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool CLEyeCameraStop(IntPtr camera);
    [DllImport("CLEyeMulticam.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool CLEyeCameraLED(IntPtr camera, bool on);
    [DllImport("CLEyeMulticam.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool CLEyeSetCameraParameter(IntPtr camera, CLEyeCameraParameter param, int value);
    [DllImport("CLEyeMulticam.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int CLEyeGetCameraParameter(IntPtr camera, CLEyeCameraParameter param);
    [DllImport("CLEyeMulticam.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool CLEyeCameraGetFrameDimensions(IntPtr camera, ref int width, ref int height);
    [DllImport("CLEyeMulticam.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool CLEyeCameraGetFrame(IntPtr camera, IntPtr pData, int waitTimeout);

    [DllImport("kernel32.dll")]
    static extern void RtlZeroMemory(IntPtr dst, int length);
    #endregion
  }
}
